﻿namespace NTLib.Core.Pool
{
    using Extensions;
    using Reflection;
    using Strings;
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;

    /// <summary>
    /// Pool instance that implement the <see cref="IPool"/> interface.
    /// </summary>
    [DataContract]
    public abstract class BasePool : IPool
    {
        #region Fields

        /// <summary>
        /// The synchronize pool object
        /// </summary>
        protected readonly object _syncPoolObject;

        /// <summary>
        /// The items
        /// </summary>
        protected readonly HashSet<IPoolItem> _items;

        private uint _minItems;
        private uint _maxItems;

        #endregion

        #region CTOR

        /// <summary>
        /// Initializes a new instance of the <see cref="Pool"/> class.
        /// </summary>
        protected BasePool()
        {
            this._syncPoolObject = new object();
            this._items = new HashSet<IPoolItem>();
            this._minItems = 0;
            this._maxItems = uint.MaxValue;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the minimum items the pool must have in background.
        /// </summary>
        /// <value>
        /// The minimum items.
        /// </value>
        /// <exception cref="System.IndexOutOfRangeException">Raised if the min value is upper than the max value.</exception>
        [IgnoreDataMember]
        public uint MinItems
        {
            get { return this._minItems; }
            set
            {
                if (this._minItems != value)
                {
                    if (value > MaxItems)
                        throw new IndexOutOfRangeException();

                    this._minItems = value;
                    this.Clean();
                }
            }
        }

        /// <summary>
        /// Gets or sets the maximum items the pool must have in background.
        /// </summary>
        /// <value>
        /// The maximum items.
        /// </value>
        /// <exception cref="System.IndexOutOfRangeException">Raised if the max value is lower than the min value.</exception>
        [IgnoreDataMember]
        public uint MaxItems
        {
            get { return this._maxItems; }
            set
            {
                if (this._maxItems != value)
                {
                    if (value < MinItems)
                        throw new IndexOutOfRangeException();

                    this._maxItems = value;
                    this.Clean();
                }
            }
        }

        /// <summary>
        /// Gets the used object count.
        /// </summary>
        /// <value>
        /// The used object count.
        /// </value>
        [IgnoreDataMember]
        public uint UsedObjectCount
        {
            get
            {
                lock (this._syncPoolObject)
                {
                    return (uint)this._items.Count;
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Cleans this instance.
        /// </summary>
        public void Clean()
        {
            ICollection<IPoolItem> toDestroy = null;
            lock (this._syncPoolObject)
            {
                toDestroy = CleanAll();
            }

            if (toDestroy != null)
            {
                foreach (var elem in toDestroy)
                {
                    try
                    {
                        elem.Destroy();
                    }
                    catch
                    {

                    }
                }
            }
        }

        /// <summary>
        /// Destroys the specified pool item.
        /// After calling this method the instance will not be able.
        /// </summary>
        /// <param name="poolItem">The pool item.</param>
        public void Destroy(IPoolItem poolItem)
        {
            lock (this._syncPoolObject)
            {
                if (this._items.Contains(poolItem))
                    this._items.Remove(poolItem);

                RemoveItemFromRecycle(poolItem);
            }
        }

        /// <summary>
        /// Gets an instance of the <see name="IPoolItem" /> Pool Item.
        /// </summary>
        /// <returns>
        /// Return a pool item type <see name="IPoolItem" />
        /// </returns>
        public T Get<T>()
            where T : IPoolItem, new()
        {
            return (T)GetByTypeImpl(typeof(T));
        }

        /// <summary>
        /// Gets the type of the by.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        /// <exception cref="InvalidCastException"></exception>
        public IPoolItem GetByType(Type type)
        {
            if (!type.IsOrInheritOf(typeof(IPoolItem)))
                throw new InvalidCastException(ExceptionResources.BadTypeException.Argument(typeof(IPoolItem)));

            return GetByTypeImpl(type);
        }

        /// <summary>
        /// Gets the by type implementation.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        /// <exception cref="InvalidCastException"></exception>
        private IPoolItem GetByTypeImpl(Type type)
        {
            if (!type.IsOrInheritOf(typeof(IPoolItem)))
                throw new InvalidCastException(ExceptionResources.BadTypeException.Argument(typeof(IPoolItem)));

            IPoolItem item = null;
            Clean();

            lock (this._syncPoolObject)
            {
                var minItems = MinItems;

                item = GetItemFromRecycle(type);

                if (item == null)
                    item = Activator.CreateInstance(type) as IPoolItem;

                this._items.Add(item);
            }
            item.Reset(this);
            return (IPoolItem)item;
        }

        /// <summary>
        /// Recycles the specified pool item.
        /// </summary>
        /// <param name="poolItem">The pool item.</param>
        public void Recycle(IPoolItem poolItem)
        {
            bool toDestroy = false;
            lock (this._syncPoolObject)
            {
                if (this._items.Contains(poolItem))
                    this._items.Remove(poolItem);

                if (!SetToTheRecycleStorage(poolItem))
                    toDestroy = true;
            }

            if (toDestroy)
                Destroy(poolItem);
        }

        /// <summary>
        /// Sets the item to the recycle storage.
        /// </summary>
        /// <param name="item">The item to recycle.</param>
        /// <returns><c>True</c> if the item have been recycle; otherwise false;</returns>
        protected abstract bool SetToTheRecycleStorage(IPoolItem item);

        /// <summary>
        /// Removes the item from recycle storage.
        /// </summary>
        /// <param name="item">The item to remove from the recycle storage</param>
        protected abstract void RemoveItemFromRecycle(IPoolItem item);

        /// <summary>
        /// Cleans all.
        /// </summary>
        /// <returns>Return all the items in the recycle storage to be destroy</returns>
        protected abstract ICollection<IPoolItem> CleanAll();

        /// <summary>
        /// Gets item from the recycle storage
        /// </summary>
        /// <returns><c>T Instance</c> if the recycle container contains one; otherwise <c>null</c></returns>
        protected abstract IPoolItem GetItemFromRecycle<T>()
                    where T : IPoolItem, new();

        /// <summary>
        /// Gets item from the recycle storage
        /// </summary>
        /// <returns><c>T Instance</c> if the recycle container contains one; otherwise <c>null</c></returns>
        protected abstract IPoolItem GetItemFromRecycle(Type type);

        #endregion
    }
}
